Visualização de Dados: Do Caos à Clareza

Tutorial Completo com Datasets Financeiros Reais do Kaggle

Author

Workshop de Visualização

🎯 Objetivo da Apresentação

  • Mostrar como NÃO fazer visualizações
  • Corrigir passo a passo aplicando boas práticas
  • Comparar implementação em Python e R (rodando dentro de Julia!)
  • Entender quando usar cada tipo de gráfico
  • Análise completa de portfolio financeiro
Note

Dataset Kaggle utilizado:


📦 Setup Inicial

#| echo: true
#| output: false
#| warning: false

using Pkg

# Instalar pacotes necessários (descomentar na primeira vez)
Pkg.add(["CSV", "DataFrames", "Plots", "StatsPlots", "Statistics", "Distributions", "GLM", "Dates", "StatsBase", "PyCall", "RCall"])

using CSV, DataFrames, Plots, StatsPlots, Statistics
using Distributions, GLM, Dates, StatsBase
using PyCall, RCall

# Configurar Python e R
ENV["PYTHON"] = "" # usar conda python
Pkg.build("PyCall")

println("✅ Pacotes carregados com sucesso!")

📊 Tipos de Variáveis → Tipos de Gráficos

Variáveis Gráfico Recomendado Quando Usar
1 Quantitativa Histograma, Densidade, Boxplot Distribuição
1 Categórica Barras Frequências
2 Quantitativas Scatter, Hexbin Relação/Correlação
1 Quant + 1 Cat Boxplot/Violin por grupo Comparar grupos
Temporal Linha, Área Evolução no tempo
Correlação múltipla Heatmap Matriz de correlação

😱 PARTE 1: O Gráfico do Inferno (JULIA)

Tudo que pode dar errado em um scatter plot

#| echo: true
#| output: true
#| warning: false

# Carregar dados
try
    global df = CSV.read("apple_stock.csv", DataFrame)
catch e
    println("⚠️  Usando dados sintéticos (arquivo não encontrado)")
    global df = DataFrame(
        Volume = rand(1e6:1e8, 1000),
        Close = rand(50:200, 1000) .+ randn(1000) .* 10
    )
end

# ❌ O PIOR GRÁFICO POSSÍVEL
scatter(df.Volume, df.Close,
    title="graf",  # ❌ Título não informativo
    xlabel="x",    # ❌ Sem contexto
    ylabel="y",    # ❌ Sem unidades
    color=:rainbow,  # ❌ Cores aleatórias
    markersize=rand(1:20, length(df.Volume)),  # ❌ Tamanhos variados
    alpha=1.0,     # ❌ Sem transparência
    legend=false,
    grid=false,    # ❌ Sem grid
    background_color=:yellow,  # ❌ Fundo horrível
    foreground_color=:red,     # ❌ Texto ilegível
    titlefontsize=8,  # ❌ Fonte minúscula
    tickfontsize=5,   # ❌ Números ilegíveis
    size=(400, 300))  # ❌ Muito pequeno
savefig("julia_helll.png")

Gráfico gerado pelo Julia


😱 Mesmo Gráfico em PYTHON (rodando no Julia!)

#| echo: true
#| output: true
#| warning: false

using PyCall

# Rodar Python dentro do Julia usando PyCall
py"""
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
matplotlib.use('Agg')  # Backend não-interativo

# Carregar dados
try:
    df = pd.read_csv('apple_stock.csv')
except:
    df = pd.DataFrame({
        'Volume': np.random.randint(1e6, 1e8, 1000),
        'Close': np.random.randint(50, 200, 1000) + np.random.randn(1000) * 10
    })

# ❌ O PIOR GRÁFICO POSSÍVEL
fig, ax = plt.subplots(figsize=(4, 3), dpi=50)
colors = plt.cm.rainbow(np.linspace(0, 1, len(df)))
sizes = np.random.randint(1, 20, size=len(df))
ax.scatter(df['Volume'], df['Close'], c=colors, s=sizes, alpha=1.0)
ax.set_title('graf', fontsize=8, color='red')
ax.set_xlabel('x', fontsize=5)
ax.set_ylabel('y', fontsize=5)
ax.set_facecolor('yellow')
ax.grid(False)
ax.tick_params(labelsize=5)
plt.tight_layout()
plt.savefig('python_hell.png', dpi=50)
plt.close()
"""

println("✅ Gráfico Python gerado!")

Gráfico gerado pelo Python


😱 Mesmo Gráfico em R (rodando no Julia!)

#| echo: true
#| output: true
#| warning: false

Pkg.build("RCall")
current_dir = pwd()
println("📁 Diretório atual: ", current_dir)
# Rodar R dentro do Julia usando RCall
R"""
library(ggplot2)

# Carregar dados
tryCatch({
  df <- read.csv('apple_stock.csv')
}, error = function(e) {
  df <<- data.frame(
    Volume = sample(1e6:1e8, 1000, replace = TRUE),
    Close = sample(50:200, 1000, replace = TRUE) + rnorm(1000) * 10
  )
})

df$color_id <- 1:nrow(df)
df$size_random <- sample(1:20, nrow(df), replace = TRUE)

# ❌ O PIOR GRÁFICO POSSÍVEL
p <- ggplot(df, aes(x = Volume, y = Close)) +
  geom_point(aes(color = color_id, size = size_random), alpha = 1.0) +
  scale_color_gradientn(colors = rainbow(100)) +
  labs(title = 'graf', x = 'x', y = 'y') +
  theme(
    plot.background = element_rect(fill = 'yellow'),
    panel.background = element_rect(fill = 'yellow'),
    panel.grid = element_blank(),
    axis.text = element_text(size = 5, color = 'red'),
    plot.title = element_text(size = 8, color = 'red'),
    legend.position = 'none'
  )

ggsave('r_hell.png', p, width = 4, height = 3, dpi = 50)
"""

# Exibir a imagem gerada
try
    display("image/png", read("r_hell.png"))
    println("✅ Gráfico R gerado!")
catch
    println("⚠️  RCall não configurado. Instale com: Pkg.build(\"RCall\")")
end

Gráfico gerado pelo R


💡 Comparação Visual: 3 Linguagens, 1 Gráfico Horrível

Julia

Python

R

Todos igualmente ruins! 😱


✅ CORREÇÃO 1: Títulos e Labels (JULIA)

#| echo: true
#| output: true
#| warning: false

using CSV, DataFrames, Plots

# Carregar dados
try
    global df = CSV.read("apple_stock.csv", DataFrame)
catch e
    println("⚠️  Usando dados sintéticos (arquivo não encontrado)")
    global df = DataFrame(
        Volume = rand(1_000_000:100_000_000, 1000),
        Close = rand(50:200, 1000) .+ randn(1000) .* 10
    )
end

# ✅ MELHORIA: Título e eixos descritivos
scatter(df.Volume, df.Close,
    title="AAPL - Relação entre Volume e Preço de Fechamento",  # ✅
    xlabel="Volume Negociado (unidades)",  # ✅
    ylabel="Preço de Fechamento (USD)",  # ✅
    color=:rainbow,  # Ainda ruim, mas vamos corrigir depois
    markersize=rand(1:20, length(df.Volume)),
    alpha=1.0,
    legend=false,
    grid=false,
    background_color=:yellow,
    foreground_color=:red,
    titlefontsize=8,
    tickfontsize=5,
    size=(400, 300))
savefig("julia_v1.png")

Gráfico Julia - versão 1


✅ CORREÇÃO 1: Títulos e Labels (PYTHON)

#| echo: true
#| output: true
#| warning: false

using PyCall

py"""
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
matplotlib.use('Agg')

# Recarregar dados (Python precisa do próprio df)
try:
    df = pd.read_csv('apple_stock.csv')
except:
    df = pd.DataFrame({
        'Volume': np.random.randint(1e6, 1e8, 1000),
        'Close': np.random.randint(50, 200, 1000) + np.random.randn(1000) * 10
    })

fig, ax = plt.subplots(figsize=(4, 3), dpi=50)
colors = plt.cm.rainbow(np.linspace(0, 1, len(df)))
sizes = np.random.randint(1, 20, size=len(df))
ax.scatter(df['Volume'], df['Close'], c=colors, s=sizes, alpha=1.0)

# ✅ MELHORIAS
ax.set_title('AAPL - Relação entre Volume e Preço de Fechamento', fontsize=8, color='red')
ax.set_xlabel('Volume Negociado (unidades)', fontsize=5)
ax.set_ylabel('Preço de Fechamento (USD)', fontsize=5)
ax.set_facecolor('yellow')
ax.grid(False)
ax.tick_params(labelsize=5)
plt.tight_layout()
plt.savefig('python_v1.png', dpi=50)
plt.close()
"""

# Verificar
if isfile("python_v1.png")
    println("✅ Gráfico Python v1 gerado!")
else
    println("❌ Arquivo não criado")
end

Gráfico Python - versão 1


✅ CORREÇÃO 1: Títulos e Labels (R)

#| echo: true
#| output: true
#| warning: false

using RCall

R"""
library(ggplot2)

# Recarregar dados no R - CORREÇÃO: garantir que df sempre existe
df <- NULL
tryCatch({
  df <- read.csv('apple_stock.csv')
}, error = function(e) {
  df <<- data.frame(
    Volume = sample(1e6:1e8, 1000, replace = TRUE),
    Close = sample(50:200, 1000, replace = TRUE) + rnorm(1000) * 10
  )
})

# Se df ainda for NULL, criar dados sintéticos
if (is.null(df)) {
  df <- data.frame(
    Volume = sample(1e6:1e8, 1000, replace = TRUE),
    Close = sample(50:200, 1000, replace = TRUE) + rnorm(1000) * 10
  )
}

df$color_id <- 1:nrow(df)
df$size_random <- sample(1:20, nrow(df), replace = TRUE)

p <- ggplot(df, aes(x = Volume, y = Close)) +
  geom_point(aes(color = color_id, size = size_random), alpha = 1.0) +
  scale_color_gradientn(colors = rainbow(100)) +
  # ✅ MELHORIAS
  labs(title = 'AAPL - Relação entre Volume e Preço de Fechamento',
       x = 'Volume Negociado (unidades)',
       y = 'Preço de Fechamento (USD)') +
  theme(
    plot.background = element_rect(fill = 'yellow'),
    panel.background = element_rect(fill = 'yellow'),
    panel.grid = element_blank(),
    axis.text = element_text(size = 5, color = 'red'),
    plot.title = element_text(size = 8, color = 'red'),
    legend.position = 'none'
  )

ggsave('r_v1.png', p, width = 4, height = 3, dpi = 50)
"""

# Verificar
if isfile("r_v1.png")
    println("✅ Gráfico R v1 gerado!")
else
    println("❌ Arquivo não criado")
end

Gráfico R - versão 1


✅ CORREÇÃO 2: Cores com Propósito (Python)

#| echo: true
#| output: true
using PyCall

py"""
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
matplotlib.use('Agg')

# Recarregar dados
try:
    df = pd.read_csv('apple_stock.csv')
except:
    df = pd.DataFrame({
        'Volume': np.random.randint(1e6, 1e8, 1000),
        'Close': np.random.randint(50, 200, 1000) + np.random.randn(1000) * 10
    })

fig, ax = plt.subplots(figsize=(4, 3), dpi=50)
sizes = np.random.randint(1, 20, size=len(df))

# ✅ Cor consistente
ax.scatter(df['Volume'], df['Close'], c='steelblue', s=sizes, alpha=1.0)
ax.set_title('AAPL - Relação entre Volume e Preço de Fechamento', 
             fontsize=8, color='red')
ax.set_xlabel('Volume Negociado (unidades)', fontsize=5)
ax.set_ylabel('Preço de Fechamento (USD)', fontsize=5)
ax.set_facecolor('yellow')
ax.grid(False)
ax.tick_params(labelsize=5)
plt.tight_layout()
plt.savefig('python_v2.png', dpi=50)
plt.close()
"""

# Verificar
if isfile("python_v2.png")
    println("✅ Gráfico Python v2 gerado!")
else
    println("❌ Arquivo não criado")
end

Gráfico Python - versão 2


✅ CORREÇÃO 2: Cores com Propósito (R)

#| echo: true
#| output: true
#| warning: false

using RCall

R"""
library(ggplot2)

# Recarregar dados
tryCatch({
  df <- read.csv('apple_stock.csv')
}, error = function(e) {
  df <<- data.frame(
    Volume = sample(1e6:1e8, 1000, replace = TRUE),
    Close = sample(50:200, 1000, replace = TRUE) + rnorm(1000) * 10
  )
})

df$size_random <- sample(1:20, nrow(df), replace = TRUE)

p <- ggplot(df, aes(x = Volume, y = Close)) +
  # ✅ Cor consistente
  geom_point(aes(size = size_random), color = 'steelblue', alpha = 1.0) +
  labs(title = 'AAPL - Relação entre Volume e Preço de Fechamento',
       x = 'Volume Negociado (unidades)',
       y = 'Preço de Fechamento (USD)') +
  theme(
    plot.background = element_rect(fill = 'yellow'),
    panel.background = element_rect(fill = 'yellow'),
    panel.grid = element_blank(),
    axis.text = element_text(size = 5, color = 'red'),
    plot.title = element_text(size = 8, color = 'red'),
    legend.position = 'none'
  )

ggsave('r_v2.png', p, width = 4, height = 3, dpi = 50)
"""

# Verificar
if isfile("r_v2.png")
    println("✅ Gráfico R v2 gerado!")
else
    println("❌ Arquivo não criado")
end

Gráfico R - versão 2


✅ CORREÇÃO 3: Transparência e Tamanho (JULIA)

#| echo: true
#| output: true

scatter(df.Volume, df.Close,
    title="AAPL - Relação entre Volume e Preço de Fechamento",
    xlabel="Volume Negociado (unidades)",
    ylabel="Preço de Fechamento (USD)",
    color=:steelblue,
    markersize=4,  # ✅ Tamanho consistente
    alpha=0.5,     # ✅ Transparência
    legend=false,
    grid=false,
    background_color=:yellow,
    foreground_color=:red,
    titlefontsize=8,
    tickfontsize=5,
    size=(400, 300))

✅ CORREÇÃO FINAL: Gráfico Perfeito (JULIA)

#| echo: true
#| output: true

# Limpar dados
df_clean = dropmissing(df[!, [:Close, :Volume]])
df_clean = df_clean[df_clean.Volume .> 0, :]
df_sample = last(df_clean, 500)

# 🎉 GRÁFICO PERFEITO
scatter(df_sample.Volume, df_sample.Close,
    title="AAPL - Relação entre Volume e Preço de Fechamento",
    xlabel="Volume Negociado (unidades)",
    ylabel="Preço de Fechamento (USD)",
    color=:steelblue,     # ✅
    markersize=4,         # ✅
    alpha=0.6,            # ✅
    legend=false,
    grid=true,            # ✅
    background_color=:white,  # ✅
    foreground_color=:black,  # ✅
    titlefontsize=14,     # ✅
    tickfontsize=10,      # ✅
    guidefontsize=12,     # ✅
    size=(1000, 700),     # ✅
    dpi=300)              # ✅
savefig("julia_final.png")

Gráfico Julia - Versão Final (perfeita)


✅ CORREÇÃO FINAL: Gráfico Perfeito (PYTHON)

#| echo: true
#| output: true
#| warning: false

using PyCall

py"""
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np

# Recarregar dados
try:
    df = pd.read_csv('apple_stock.csv')
except:
    df = pd.DataFrame({
        'Volume': np.random.randint(1e6, 1e8, 1000),
        'Close': np.random.randint(50, 200, 1000) + np.random.randn(1000) * 10
    })

# Limpar dados
df_clean = df.dropna(subset=['Close', 'Volume'])
df_clean = df_clean[df_clean['Volume'] > 0].tail(500)

# 🎉 GRÁFICO PERFEITO
fig, ax = plt.subplots(figsize=(10, 7), dpi=300)
ax.scatter(df_clean['Volume'], df_clean['Close'], 
           c='steelblue', s=16, alpha=0.6)
ax.set_title('AAPL - Relação entre Volume e Preço de Fechamento', fontsize=14)
ax.set_xlabel('Volume Negociado (unidades)', fontsize=12)
ax.set_ylabel('Preço de Fechamento (USD)', fontsize=12)
ax.set_facecolor('white')
ax.grid(True, alpha=0.3)
ax.tick_params(labelsize=10)
plt.tight_layout()
plt.savefig('python_final.png', dpi=300, bbox_inches='tight')
plt.close()
"""

# Verificar
if isfile("python_final.png")
    println("✅ Gráfico Python perfeito gerado!")
else
    println("❌ Arquivo não criado")
end

Gráfico Python - Versão Final (perfeita)


✅ CORREÇÃO FINAL: Gráfico Perfeito (R)

#| echo: true
#| output: true
#| warning: false

using RCall

R"""
library(ggplot2)

# Recarregar dados
tryCatch({
  df <- read.csv('apple_stock.csv')
}, error = function(e) {
  df <- data.frame(
    Volume = sample(1e6:1e8, 1000, replace = TRUE),
    Close = sample(50:200, 1000, replace = TRUE) + rnorm(1000) * 10
  )
})

# Limpar dados
df_clean <- df[!is.na(df$Volume) & !is.na(df$Close) & df$Volume > 0, ]
df_clean <- tail(df_clean, 500)

# 🎉 GRÁFICO PERFEITO
p <- ggplot(df_clean, aes(x = Volume, y = Close)) +
  geom_point(color = 'steelblue', size = 2, alpha = 0.6) +
  labs(title = 'AAPL - Relação entre Volume e Preço de Fechamento',
       x = 'Volume Negociado (unidades)',
       y = 'Preço de Fechamento (USD)') +
  theme_minimal() +
  theme(
    plot.title = element_text(size = 14, hjust = 0.5),
    axis.title = element_text(size = 12),
    axis.text = element_text(size = 10),
    panel.grid.major = element_line(color = 'gray80', linewidth = 0.5),  # ✅ Corrigido
    panel.grid.minor = element_blank(),
    plot.background = element_rect(fill = 'white', color = NA),
    panel.background = element_rect(fill = 'white')
  )

ggsave('r_final.png', p, width = 10, height = 7, dpi = 300)
"""

# Verificar
if isfile("r_final.png")
    println("✅ Gráfico R perfeito gerado!")
else
    println("❌ Arquivo não criado")
end

Gráfico R - Versão Final (perfeita)


🎨 Checklist de Boas Práticas

Título descritivo: O que, onde, quando
Eixos claros: Variável + unidade
Cores com propósito: Máx 3-5 cores
Transparência: Para ver sobreposições
Tamanho adequado: Mín 800x600px
Fonte legível: Mín 10pt
Grid: Facilita leitura
Fundo neutro: Branco ou cinza claro
Alta resolução: DPI ≥ 300


🎯 Próximos Slides

Nos próximos slides veremos:

  1. Regressão Linear (2 quantitativas)
  2. Distribuições (1 quantitativa)
  3. Boxplot/Violin (1 quant + 1 cat)
  4. Correlação (heatmap)
  5. Séries Temporais (temporal)
  6. Diagnóstico de Modelos
  7. Dashboard EDA Completo

Sempre comparando Julia, Python e R!


Continue Explorando

Os próximos slides têm análises completas com os 3 ambientes rodando em paralelo!

:::

🐍 Python: Regressão Linear

#| echo: true
#| output: true
#| eval: true
using PyCall

println("🐍 Regressão Python com apple_stock.csv...\n")

py"""
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import os

# Carregar arquivo apple_stock.csv
if not os.path.exists('apple_stock.csv'):
    raise FileNotFoundError("❌ Arquivo 'apple_stock.csv' não encontrado!")

df = pd.read_csv('apple_stock.csv')
print(f"✅ apple_stock.csv carregado")
print(f"   Colunas: {list(df.columns)}")
print(f"   Linhas: {len(df)}")

# Limpar
df = df.dropna(subset=['Close', 'Volume'])
df = df[df['Volume'] > 0].tail(500)

x = df['Volume'].values
y = df['Close'].values

print(f"📈 {len(df)} registros após limpeza")
print(f"   Volume: {x.min():.0f} - {x.max():.0f}")
print(f"   Close: {y.min():.2f} - {y.max():.2f}\n")

# REGRESSÃO LINEAR MANUAL
n = len(x)
x_mean = np.mean(x)
y_mean = np.mean(y)

# Calcular slope (b) e intercept (a)
numerator = np.sum((x - x_mean) * (y - y_mean))
denominator = np.sum((x - x_mean) ** 2)
slope = numerator / denominator
intercept = y_mean - slope * x_mean

# R²
y_pred = slope * x + intercept
ss_res = np.sum((y - y_pred) ** 2)
ss_tot = np.sum((y - y_mean) ** 2)
r_squared = 1 - (ss_res / ss_tot)

print(f"📊 Slope: {slope:.2e}")
print(f"📊 Intercept: {intercept:.2f}")
print(f"📊 R²: {r_squared:.4f}\n")

# GRÁFICO
fig, ax = plt.subplots(figsize=(10, 7), dpi=150)

# Scatter
ax.scatter(x, y, alpha=0.6, color='steelblue', s=20, label='Dados')

# Linha de regressão
x_line = np.linspace(x.min(), x.max(), 100)
y_line = slope * x_line + intercept
ax.plot(x_line, y_line, color='red', linewidth=3, label='Regressão')

# Títulos
ax.set_title('Regressão Linear: Preço vs Volume (PYTHON)', 
             fontsize=14, fontweight='bold')
ax.set_xlabel('Volume Negociado', fontsize=12)
ax.set_ylabel('Preço Fechamento (USD)', fontsize=12)
ax.grid(True, alpha=0.3, linestyle='--')
ax.legend()

# Texto
text = f'y = {intercept:.2f} + {slope:.2e}×Volume\\nR² = {r_squared:.4f}'
ax.text(0.05, 0.95, text,
        transform=ax.transAxes,
        fontsize=11,
        verticalalignment='top',
        bbox=dict(boxstyle='round', facecolor='white', alpha=0.9),
        color='red',
        fontweight='bold')

plt.tight_layout()
plt.savefig('slide3_python_regression.png', dpi=150, bbox_inches='tight')
print("✅ slide3_python_regression.png")
plt.close()
"""

if isfile("slide3_python_regression.png")
    println("\n✅ SUCESSO!")
else
    println("\n❌ Falhou")
end

Python - Regressão Linear


📈 R: Regressão Linear

#| echo: true
#| output: true
#| eval: true

using RCall

println("📊 Executando R com apple_stock.csv...\n")

R"""
library(ggplot2)

# Carregar arquivo apple_stock.csv
if (!file.exists('apple_stock.csv')) {
  stop("❌ Arquivo 'apple_stock.csv' não encontrado!")
}

df <- read.csv('apple_stock.csv')
cat("✅ apple_stock.csv carregado\n")
cat("   Colunas:", paste(names(df), collapse=", "), "\n")
cat("   Linhas:", nrow(df), "\n")

# Limpar dados
df <- na.omit(df[, c('Close', 'Volume')])
df <- df[df$Volume > 0, ]
df <- tail(df, 500)

cat("📈", nrow(df), "registros após limpeza\n")
cat("   Volume:", format(min(df$Volume), scientific=FALSE), "-", 
    format(max(df$Volume), scientific=FALSE), "\n")
cat("   Close:", round(min(df$Close), 2), "-", round(max(df$Close), 2), "\n\n")

# Calcular regressão
model <- lm(Close ~ Volume, data=df)
r2 <- summary(model)$r.squared
coefs <- coef(model)

cat("📊 Slope:", format(coefs[2], scientific=TRUE), "\n")
cat("📊 Intercept:", round(coefs[1], 2), "\n")
cat("📊 R²:", round(r2, 4), "\n\n")

# CRIAR GRÁFICO
p <- ggplot(df, aes(x=Volume, y=Close)) +
  geom_point(alpha=0.6, color='steelblue', size=2) +
  geom_smooth(method='lm', color='red', linewidth=1.5, 
              se=TRUE, fill='pink', alpha=0.2) +
  labs(
    title='Regressão Linear: Preço vs Volume (R)',
    x='Volume Negociado',
    y='Preço Fechamento (USD)'
  ) +
  annotate('text', 
           x=min(df$Volume) + (max(df$Volume) - min(df$Volume)) * 0.05,
           y=max(df$Close) * 0.95,
           label=paste0(
             'y = ', round(coefs[1], 2), ' + ',
             format(coefs[2], scientific=TRUE, digits=2), '×Volume\n',
             'R² = ', round(r2, 4)
           ),
           hjust=0, vjust=1,
           color='red', size=4, fontface='bold',
           fill='white', alpha=0.8) +
  theme_minimal() +
  theme(
    plot.title=element_text(size=14, face='bold'),
    axis.title=element_text(size=12),
    panel.grid.major=element_line(color='gray90'),
    panel.grid.minor=element_blank()
  )

# Salvar
ggsave('slide3_r_regression.png', plot=p, 
       width=10, height=7, dpi=150)

cat("✅ slide3_r_regression.png\n")
"""

# Verificar
println("\n" * "="^60)
if isfile("slide3_r_regression.png")
    println("✅ SUCESSO! Gráfico R gerado")
    println("📂 slide3_r_regression.png")
else
    println("❌ Arquivo não criado")
    println("\n💡 Possíveis problemas:")
    println("   1. RCall não instalado: using Pkg; Pkg.add(\"RCall\")")
    println("   2. R não tem ggplot2: install.packages(\"ggplot2\")")
    println("   3. Erro de permissão de escrita")
end

R - Regressão Linear


📊 SLIDE 5: Distribuições - Histograma vs Densidade

Dataset: Yahoo Stock Market Data - Kaggle
Tipo de variável: 1 Quantitativa → Histograma + Densidade

#| echo: true
#| output: true
#| eval: true

#| echo: true
#| output: true

using CSV, DataFrames, Plots, Statistics, Distributions

println("📊 Carregando dados de ações...\n")

# Função para calcular retornos
calc_returns(prices) = [0; diff(log.(prices)) .* 100]

# Tickers para analisar
tickers = ["AAPL", "TSLA", "SPY"]

# Dicionário para armazenar retornos
returns_dict = Dict{String, Vector{Float64}}()

# Carregar cada ticker
for ticker in tickers
    # Tentar diferentes variações de nome
    possible_files = [
        "$(ticker).csv",
        "$(lowercase(ticker)).csv",
        "yahoo-stock-market-data/$(ticker).csv",
        "stocks_data/$(ticker).csv"
    ]
    
    found = false
    for filepath in possible_files
        if isfile(filepath)
            try
                df = CSV.read(filepath, DataFrame)
                
                # Calcular retornos
                returns = calc_returns(df.Close)[2:end]
                returns_dict[ticker] = returns
                
                println("✅ $ticker: $(length(returns)) retornos")
                found = true
                break
            catch e
                println("⚠️  Erro ao processar $filepath: $e")
            end
        end
    end
    
    if !found
        println("❌ Não encontrado: $ticker")
    end
end

println("\n📈 Total carregado: $(length(returns_dict)) ações\n")

# Criar plots individuais
plots_list = []

for ticker in tickers
    if haskey(returns_dict, ticker)
        returns = returns_dict[ticker]
        
        # Cor por ticker
        colors = Dict("AAPL" => :steelblue, "TSLA" => :purple, "SPY" => :coral)
        color = get(colors, ticker, :blue)
        
        p = histogram(returns, bins=50, normalize=:pdf,
            alpha=0.6, label="Histogram", 
            title="$ticker - Retornos Diários",
            xlabel="Retorno (%)", ylabel="Densidade",
            color=color, 
            legend=:topright)
        
        # KDE
        density!(p, returns, linewidth=3, label="KDE", color=:red)
        
        # Normal teórica
        plot!(p, Normal(mean(returns), std(returns)),
            linewidth=2, label="Normal Teórica", 
            linestyle=:dash, color=:black)
        
        # Estatísticas
        annotate!(p, minimum(returns) * 0.8, maximum(returns) * 0.8,
            text("μ = $(round(mean(returns), digits=3))%\nσ = $(round(std(returns), digits=3))%",
                 :left, 9, :darkgray))
        
        push!(plots_list, p)
    else
        # Plot vazio se não encontrou
        p = plot(title="$ticker\n(Não disponível)", 
                legend=false, grid=false, showaxis=false)
        push!(plots_list, p)
    end
end

# Combinar plots
if length(plots_list) >= 3
    p_final = plot(plots_list[1], plots_list[2], plots_list[3],
        layout=(1, 3), size=(1500, 500))
else
    p_final = plot(plots_list..., layout=(1, length(plots_list)))
end

# Salvar
savefig(p_final, "distributions_returns.png")
println("✅ Gráfico salvo: distributions_returns.png")

# Mostrar
display(p_final)

# Estatísticas resumo
println("\n" * "="^60)
println("ESTATÍSTICAS DE RETORNOS")
println("="^60)
for ticker in tickers
    if haskey(returns_dict, ticker)
        r = returns_dict[ticker]
        println("$ticker:")
        println("  Média: $(round(mean(r), digits=4))%")
        println("  Std:   $(round(std(r), digits=4))%")
        println("  Min:   $(round(minimum(r), digits=2))%")
        println("  Max:   $(round(maximum(r), digits=2))%")
        println()
    end
end

Gráfico de Estatística de Retornos


📊 Comparação: AAPL, TSLA, SPY

#| echo: true
#| output: true
#| eval: true

using StatsPlots, Distributions

println("📊 Criando distribuições de retornos...\n")

# Verificar quais dados foram carregados
if !@isdefined(returns_dict)
    println("⚠️  Carregando dados primeiro...")
    
    # Carregar dados
    tickers = ["AAPL", "TSLA", "SPY"]
    returns_dict = Dict{String, Vector{Float64}}()
    
    for ticker in tickers
        possible_files = [
            "$(ticker).csv",
            "$(lowercase(ticker)).csv",
            "yahoo-stock-market-data/$(ticker).csv"
        ]
        
        for filepath in possible_files
            if isfile(filepath)
                try
                    df = CSV.read(filepath, DataFrame)
                    returns = calc_returns(df.Close)[2:end]
                    returns_dict[ticker] = returns
                    println("✅ $ticker")
                    break
                catch; end
            end
        end
    end
end

# Função helper para criar plot seguro
function create_distribution_plot(returns_dict, ticker, color=:steelblue)
    if haskey(returns_dict, ticker)
        returns = returns_dict[ticker]
        
        p = histogram(returns, bins=50, normalize=:pdf,
            alpha=0.6, 
            title="$ticker - Retornos Diários",
            xlabel="Retorno (%)", 
            ylabel="Densidade", 
            legend=:topright,
            color=color,
            label="Histogram")
        
        density!(p, returns, linewidth=3, label="KDE", color=:red)
        
        plot!(p, Normal(mean(returns), std(returns)),
            linewidth=2, label="Normal Teórica", 
            linestyle=:dash, color=:black)
        
        return p
    else
        # Plot vazio se não existir
        return plot(title="$ticker\n(Dados não disponíveis)", 
                   legend=false, grid=false, showaxis=false)
    end
end

# Criar os 3 plots
p1 = create_distribution_plot(returns_dict, "AAPL", :steelblue)
p2 = create_distribution_plot(returns_dict, "TSLA", :purple)
p3 = create_distribution_plot(returns_dict, "SPY", :coral)

# Combinar em layout 1x3
p_final = plot(p1, p2, p3, layout=(1,3), size=(1500, 500))

# Salvar
savefig(p_final, "distributions_3stocks.png")
println("\n✅ Gráfico salvo: distributions_3stocks.png")

# Mostrar
display(p_final)

📊 Comparação: AAPL, TSLA, SPY


🐍 Python: Distribuições


#| echo: true
#| output: true
#| eval: true

using PyCall

println("🐍 Distribuições Python (SEM scipy)...\n")

py"""
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import os

# Função KDE manual (sem scipy)
def kde_manual(data, bandwidth=None):
    if bandwidth is None:
        # Regra de Silverman
        bandwidth = 1.06 * np.std(data) * len(data)**(-1/5)
    
    x_range = np.linspace(data.min(), data.max(), 100)
    kde_values = np.zeros_like(x_range)
    
    for xi in x_range:
        kernel = np.exp(-0.5 * ((data - xi) / bandwidth)**2)
        kde_values[np.where(x_range == xi)] = kernel.sum() / (len(data) * bandwidth * np.sqrt(2 * np.pi))
    
    return x_range, kde_values

# Função normal PDF manual (sem scipy)
def normal_pdf(x, mu, sigma):
    return (1 / (sigma * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mu) / sigma)**2)

# Tickers para carregar
tickers = ['AAPL', 'TSLA', 'SPY']
returns_data = {}

print("📊 Carregando dados...\n")

# Carregar cada ticker
for ticker in tickers:
    possible_files = [
        f'{ticker}.csv',
        f'{ticker.lower()}.csv',
        f'yahoo-stock-market-data/{ticker}.csv',
        f'stocks_data/{ticker}.csv'
    ]
    
    found = False
    for filepath in possible_files:
        if os.path.exists(filepath):
            try:
                df = pd.read_csv(filepath)
                returns = np.diff(np.log(df['Close'])) * 100
                returns_data[ticker] = returns
                print(f"✅ {ticker}: {len(returns)} retornos")
                found = True
                break
            except Exception as e:
                print(f"⚠️  Erro: {e}")
    
    if not found:
        print(f"❌ Não encontrado: {ticker}")

print(f"\n📈 Total: {len(returns_data)} ações\n")

# Criar figura
fig, axes = plt.subplots(1, 3, figsize=(15, 5), dpi=150)

# Cores
colors = {'AAPL': 'steelblue', 'TSLA': 'purple', 'SPY': 'coral'}

for idx, ticker in enumerate(tickers):
    ax = axes[idx]
    
    if ticker in returns_data:
        returns = returns_data[ticker]
        color = colors.get(ticker, 'steelblue')
        
        # Histograma
        ax.hist(returns, bins=50, density=True, alpha=0.6, 
                label='Histogram', color=color)
        
        # KDE manual
        x_kde, y_kde = kde_manual(returns)
        ax.plot(x_kde, y_kde, 'r-', linewidth=3, label='KDE')
        
        # Normal teórica manual
        mu, sigma = returns.mean(), returns.std()
        x_norm = np.linspace(returns.min(), returns.max(), 100)
        y_norm = normal_pdf(x_norm, mu, sigma)
        ax.plot(x_norm, y_norm, 'k--', linewidth=2, label='Normal Teórica')
        
        ax.set_title(f'{ticker} - Retornos Diários', 
                     fontsize=14, fontweight='bold')
        ax.set_xlabel('Retorno (%)', fontsize=12)
        ax.set_ylabel('Densidade', fontsize=12)
        ax.legend(loc='best')
        ax.grid(True, alpha=0.3, linestyle='--')
        
        # Estatísticas
        stats_text = f'μ = {mu:.3f}%\\nσ = {sigma:.3f}%'
        ax.text(0.05, 0.95, stats_text,
                transform=ax.transAxes,
                fontsize=9,
                verticalalignment='top',
                bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
    else:
        ax.text(0.5, 0.5, f'{ticker}\\n(Não disponível)',
                ha='center', va='center', fontsize=12)
        ax.set_title(f'{ticker}', fontsize=14)
        ax.axis('off')

plt.tight_layout()
plt.savefig('slide5_python_distributions.png', dpi=150, bbox_inches='tight')
print("✅ slide5_python_distributions.png")
plt.close()
"""

if isfile("slide5_python_distributions.png")
    println("\n✅ SUCESSO!")
else
    println("\n❌ Falhou")
end

Python: Distribuições


📈 R: Distribuições

#| echo: true
#| output: true
#| eval: true

using RCall

println("📊 Distribuições R...\n")

r_dist = raw"""
library(ggplot2)
library(patchwork)

aapl <- read.csv('AAPL.csv')
tsla <- read.csv('TSLA.csv')
spy <- read.csv('SPY.csv')

returns_aapl <- diff(log(aapl$Close)) * 100
returns_tsla <- diff(log(tsla$Close)) * 100
returns_spy <- diff(log(spy$Close)) * 100

df_returns <- data.frame(
  Return = c(returns_aapl, returns_tsla, returns_spy),
  Asset = c(rep('AAPL', length(returns_aapl)),
            rep('TSLA', length(returns_tsla)),
            rep('SPY', length(returns_spy)))
)

ggplot(df_returns, aes(x = Return, fill = Asset)) +
  geom_histogram(aes(y = ..density..), bins = 50, alpha = 0.6) +
  geom_density(aes(color = Asset), linewidth = 1.5) +
  facet_wrap(~Asset, scales = 'free') +
  labs(title = 'Distribuição de Retornos',
       x = 'Retorno (%)',
       y = 'Densidade') +
  theme_minimal() +
  theme(plot.title = element_text(size = 14),
        axis.title = element_text(size = 12))

ggsave('slide5_r_distributions.png', width = 15, height = 5, dpi = 300)
"""

println(r_dist)

# Executar o código R
R"""
library(ggplot2)
library(patchwork)

aapl <- read.csv('AAPL.csv')
tsla <- read.csv('TSLA.csv')
spy <- read.csv('SPY.csv')

returns_aapl <- diff(log(aapl$Close)) * 100
returns_tsla <- diff(log(tsla$Close)) * 100
returns_spy <- diff(log(spy$Close)) * 100

df_returns <- data.frame(
  Return = c(returns_aapl, returns_tsla, returns_spy),
  Asset = c(rep('AAPL', length(returns_aapl)),
            rep('TSLA', length(returns_tsla)),
            rep('SPY', length(returns_spy)))
)

ggplot(df_returns, aes(x = Return, fill = Asset)) +
  geom_histogram(aes(y = ..density..), bins = 50, alpha = 0.6) +
  geom_density(aes(color = Asset), linewidth = 1.5) +
  facet_wrap(~Asset, scales = 'free') +
  labs(title = 'Distribuição de Retornos',
       x = 'Retorno (%)',
       y = 'Densidade') +
  theme_minimal() +
  theme(plot.title = element_text(size = 14),
        axis.title = element_text(size = 12))

ggsave('slide5_r_distributions.png', width = 15, height = 5, dpi = 300)
"""

if isfile("slide5_r_distributions.png")
    println("✅ SUCESSO!")
else
    println("❌ Falhou")
end

R: Distribuições


📊 Estatísticas Descritivas

#| echo: true
#| output: true

using StatsBase

println("=== Estatísticas de Retornos ===\n")
println("AAPL:")
println("  Média: $(round(mean(returns_aapl), digits=3))%")
println("  Std: $(round(std(returns_aapl), digits=3))%")
println("  Assimetria: $(round(skewness(returns_aapl), digits=3))")
println("  Curtose: $(round(kurtosis(returns_aapl), digits=3))")

println("\nTSLA:")
println("  Média: $(round(mean(returns_tsla), digits=3))%")
println("  Std: $(round(std(returns_tsla), digits=3))%")
println("  Assimetria: $(round(skewness(returns_tsla), digits=3))")
println("  Curtose: $(round(kurtosis(returns_tsla), digits=3))")

println("\nSPY:")
println("  Média: $(round(mean(returns_spy), digits=3))%")
println("  Std: $(round(std(returns_spy), digits=3))%")
println("  Assimetria: $(round(skewness(returns_spy), digits=3))")
println("  Curtose: $(round(kurtosis(returns_spy), digits=3))")

📚 COMPARAÇÃO: Julia vs Python vs R

#| echo: true
#| output: true

comparison = DataFrame(
    Critério = [
        "Performance (Velocidade)",
        "Facilidade - Básico",
        "Facilidade - Avançado",
        "Documentação",
        "Comunidade",
        "Integração Científica",
        "Interatividade",
        "Curva de Aprendizado",
        "Melhor para"
    ],
    Julia = [
        "⭐⭐⭐⭐⭐ (5/5)",
        "⭐⭐⭐⭐ (4/5)",
        "⭐⭐⭐ (3/5)",
        "⭐⭐⭐ (3/5)",
        "⭐⭐⭐ (3/5)",
        "⭐⭐⭐⭐⭐ (5/5)",
        "⭐⭐⭐⭐ (4/5)",
        "Média",
        "Computação intensiva, performance"
    ],
    Python = [
        "⭐⭐⭐ (3/5)",
        "⭐⭐⭐⭐⭐ (5/5)",
        "⭐⭐⭐⭐ (4/5)",
        "⭐⭐⭐⭐⭐ (5/5)",
        "⭐⭐⭐⭐⭐ (5/5)",
        "⭐⭐⭐⭐ (4/5)",
        "⭐⭐⭐⭐⭐ (5/5)",
        "Fácil",
        "Prototipagem rápida, ML/DL"
    ],
    R = [
        "⭐⭐ (2/5)",
        "⭐⭐⭐ (3/5)",
        "⭐⭐⭐⭐⭐ (5/5)",
        "⭐⭐⭐⭐⭐ (5/5)",
        "⭐⭐⭐⭐ (4/5)",
        "⭐⭐⭐⭐⭐ (5/5)",
        "⭐⭐⭐ (3/5)",
        "Média-Alta",
        "Estatística acadêmica, EDA"
    ]
)

println(comparison)

✅ CHECKLIST FINAL: Boas Práticas

ANTES DE VISUALIZAR:

✅ Entender o objetivo
✅ Conhecer o público-alvo
✅ Identificar tipo de variáveis
✅ Limpar dados
✅ Calcular estatísticas

ESCOLHA DO GRÁFICO:

✅ 1 quant → Histogram/Density
✅ 1 cat → Bar
✅ 2 quant → Scatter
✅ 1q+1c → Boxplot
✅ Temporal → Line
✅ Correlação → Heatmap

DESIGN:

✅ Título descritivo
✅ Eixos com labels/unidades
✅ Cores com propósito
✅ Transparência adequada
✅ Tamanho legível (≥800px)
✅ Fonte ≥10pt
✅ Grid quando apropriado

ÉTICA:

✅ Eixos não manipulados
✅ Escalas honestas
✅ Citar fontes
✅ Transparência total


📖 RECURSOS RECOMENDADOS

Documentação:

Livros:

  • Fundamentals of Data Visualization - Claus Wilke
  • Storytelling with Data - Cole Nussbaumer Knaflic
  • The Visual Display of Quantitative Information - Edward Tufte

Datasets:


🎯 PRÓXIMOS PASSOS

  1. Praticar com datasets reais do Kaggle
  2. Explorar visualizações interativas (Plotly, D3.js)
  3. Aprender dashboards (Pluto.jl, Streamlit, Shiny)
  4. Estudar visualização de redes e geoespacial
  5. Aplicar em projetos reais
  6. Compartilhar seus trabalhos!
Dica Final

A melhor visualização é aquela que comunica claramente sua mensagem para o público-alvo. Simplicidade > Complexidade.


Lição de Casa: 🕵 Detetive de Visualização

Missão: Encontre um gráfico real publicado online (notícia, artigo, rede social, relatório) que seja tecnicamente correto mas visualmente problemático. Analise, refaça aplicando boas práticas e justifique suas escolhas.


Parte 1: Encontrar e Documentar

Onde procurar: - Portais de notícias (G1, Folha, Estadão) - Relatórios corporativos públicos - Artigos científicos de acesso aberto - Redes sociais (Twitter/X, LinkedIn, Instagram) - Publicações governamentais

Documentação necessária: - Screenshot do gráfico original - Fonte completa (URL + data de acesso) - Contexto: onde foi publicado e para qual audiência


Parte 2: Análise Crítica

Identifique pelo menos 6 problemas de visualização:

Categorias possíveis: - Título e labels (clareza, contexto, unidades) - Escolha de cores (contraste, acessibilidade, propósito) - Tipo de gráfico (adequação aos dados) - Escalas e eixos (manipulação, truncamento) - Tamanho e proporções (legibilidade) - Elementos desnecessários (chart junk, 3D, efeitos)

Para cada problema, explique: 1. O que está errado 2. Por que isso prejudica a comunicação 3. Como deveria ser corrigido


Parte 3: Reconstrução

Refaça o gráfico aplicando boas práticas:

Requisitos mínimos: - Título descritivo com contexto completo - Eixos com labels e unidades claras - Cores apropriadas e acessíveis - Legenda clara (se aplicável) - Grid quando necessário - Tamanho adequado (mínimo 800x600px) - Fontes legíveis (mínimo 11pt) - Tipo de gráfico apropriado aos dados

Código exigido: - Forneça o código usado (Julia, Python ou R) - Dados: extraia aproximadamente ou use os originais se disponíveis - Comente as decisões de design no código


Parte 4: Justificativa

Responda em até 300 palavras:

  1. Quais foram suas principais mudanças e por quê?
  2. Que história o gráfico corrigido conta que o original obscurecia?
  3. Como suas escolhas melhoram a comunicação para a audiência original?

Bônus: Identifique se há tentativa de manipulação visual intencional (escalas truncadas, gráficos enganosos) e discuta as implicações éticas.


Entrega

Arquivo PDF contendo: 1. Gráfico original (screenshot + fonte) 2. Lista de problemas identificados 3. Gráfico reconstruído 4. Código comentado 5. Justificativa escrita


## 🙏 OBRIGADO!